/*
 *  This file is part of Fuzzy Ontology Framework Project.
 *
 *   Fuzzy Ontology Framework Project is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   Fuzzy Ontology Framework Project is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   If you want a copy of the GNU General Public License
 *   write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package fuzzyOntologyFramework;

import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.rdf.model.*;
import java.util.*;
import com.hp.hpl.jena.util.iterator.*;

/*
 * FuzSemRules class responsible for verifying fuzzy semantic rules
 * Creation date: 06/12/2006
 */

public class FuzSemRules {
	
	private FuzOntAccess fuzOntAccess;

	public FuzSemRules(OntModel ontModel) {
		fuzOntAccess = new FuzOntAccess(ontModel);
	}
	
	
	/* 
	 * returns individuals of a class considering its subclasses and fuzzy memberships
	 * @param ontClass 		Class that may have subclasses and individuals.
	 * @param minMembership The minimum membership required between classes and individuals.
	 * @return 				Hashtable containing all individuals of ontClass and their correspondent membership degrees above minMembership.
	 */
	public Hashtable getSubClassIndividuals(OntClass ontClass, float minMembership) {
		
		Hashtable result = new Hashtable();
		
		// considering all instances as crisp instances (fuzzy membership = 1)
		for(Iterator instances = ontClass.listInstances(); instances.hasNext();) {
			Individual ind = (Individual) ((RDFNode) instances.next()).as(Individual.class);
			result.put(ind, new Float(1));
		}
		
		// verifying possible fuzzy memberships
		Hashtable fuzMemberships = fuzOntAccess.getMemDegree(ontClass, minMembership);
		if (fuzMemberships != null) {
			result.putAll(fuzMemberships);
		}
		
		return result;
	}
	
	/* 
	 * returns similar individuals above minSim
	 * @param ind 		Individual that may have similar individuals.
	 * @param minSim	The minimum similarity required between individuals.
	 * @return 			Hashtable containing all individuals similar to ind and their correspondent similarity degrees above minMembership.
	 */
	public Hashtable getSimilarTo(Individual ind, float minSim) {
		/*
		// getting similar individuals independently of ancestors  
		OntProperty simTo = ontModel.getOntProperty("http://www.owl-ontologies.com/fuzzyOntology.owl#similarTo");
		return fuzOntAccess.getFuzRelDegree(ind, simTo, minSim);
		*/
		
		// getting similar individuals that have an ancestor in common
		return fuzOntAccess.getFuzSimilarity(null, ind, minSim);
				
	}
	
	/* 
	 * returns similar individuals above minSim
	 * @param superClass 	Superclass of ind that should be considered when checking similarities.
	 * @param ind 			Individual that may have similar individuals.
	 * @param minSim		The minimum similarity required between individuals.
	 * @return 				Hashtable containing all individuals similar to ind and their correspondent similarity degrees above minMembership.
	 */
	public Hashtable getSimilarTo(OntClass superClass, Individual ind, float minSim) {
		
		// getting similar individuals that have an ancestor in common
		return fuzOntAccess.getFuzSimilarity(superClass, ind, minSim);
				
	}
	
	
	/* 
	 * returns all parts of a whole (holonymy-meronymy)
	 * @param whole 		Individual representing the whole.
	 * @param minMembership The minimum membership required between whole and parts.
	 * @param minSim		The minimum similarity required between parts of the whole (getting similar parts).
	 * @return 				Hashtable containing part as keys and hashtables including the part, its corresponding membership, and all its similar parts and their similarity degrees.
	 */
	public Hashtable getParts(Individual whole, OntProperty hasPart, float minMembership, float minSim) {
		
		// return all parts of whole and their memberships >= minMembership
		Hashtable parts = fuzOntAccess.getFuzRelDegree(whole, hasPart, minMembership);
		
		// verifying parts that are similar to the parts of whole
		Hashtable simParts, result = new Hashtable();
		if (parts != null) {
			for (Enumeration e = parts.keys(); e.hasMoreElements(); ) {
				Individual part = (Individual) ((RDFNode) e.nextElement()).as(Individual.class);
				simParts = fuzOntAccess.getFuzSimilarity(null, part, minSim);
				if (simParts == null)
					simParts = new Hashtable();
				simParts.put(part, parts.get(part));
				result.put(part, simParts);
			}
		}
		 return result;
		 

	}
	
	
	/* 
	 * returns all parts of a whole (holonymy-meronymy)
	 * @param whole 		Individual representing the whole.
	 * @param minMembership The minimum membership required between whole and parts.
	 * @return 				Hashtable containing part as keys and their corresponding membership degrees to the whole.
	 */
	public Hashtable getParts(Individual whole, OntProperty hasPart, float minMembership) {
		
		// return all parts of whole and their memberships >= minMembership
		return fuzOntAccess.getFuzRelDegree(whole, hasPart, minMembership);
		
	}
	
	
	/* 
	 * returns all wholes that are composed of a part (holonymy-meronymy)
	 * @param part			Individual representing the part.
	 * @param minMembership The minimum membership required between part and wholes.
	 * @param minSim		The minimum similarity required between wholes of the part (getting similar wholes).
	 * @return 				Hashtable containing whole as key and hashtable including the part's membership to the whole and similar wholes with similarity degrees.
	 */
	public Hashtable getWholes(Individual part, OntProperty partOf, float minMembership, float minSim) {
		
		// return all wholes of part and their memberships >= minMembership
		Hashtable wholes = fuzOntAccess.getFuzRelDegree(part, partOf, minMembership);
		
		// verifying wholes similar to wholes of part
		Hashtable simWholes, result = new Hashtable();
		for (Enumeration e = wholes.keys(); e.hasMoreElements(); ) {
			Individual whole = (Individual) ((RDFNode) e.nextElement()).as(Individual.class);
			simWholes = fuzOntAccess.getFuzSimilarity(null, whole, minSim);
			if (simWholes == null)
				simWholes = new Hashtable();
			simWholes.put(whole, wholes.get(whole));
			result.put(whole, simWholes);
			
		}
		 
		return result;
		
	}
	
	
	/* 
	 * returns all wholes that are composed of a part (holonymy-meronymy)
	 * @param part			Individual representing the part.
	 * @param minMembership The minimum membership required between part and wholes.
	 * @param minSim		The minimum similarity required between wholes of the part (getting similar wholes).
	 * @return 				Hashtable containing whole as key and membership degrees.
	 */
	public Hashtable getWholes(Individual part, OntProperty partOf, float minMembership) {
		
		// return all wholes of part and their memberships >= minMembership
		return fuzOntAccess.getFuzRelDegree(part, partOf, minMembership);
		
	}
	
	
	/* 
	 * returns all individuals that are transitively related
	 * @param ind			Individual that may have transitively related individuals.
	 * @param transProp 	The transitive relationship.
	 * @param minMembership The minimum membership required between related individuals.
	 * @param minSim		The minimum similarity required between individuals that are similar to transitively related individuals.
	 * @return 				Hashtable containing transitively related individuals as keys and hashtable including the transitive part's membership degree and similar individuals with similarity degrees.
	 */
	public Hashtable getTransitiveRelated(Individual ind, OntProperty transProp, float minMembership, float minSim) {
		
		Hashtable related = fuzOntAccess.getFuzRelDegree(ind, transProp, minMembership);
		
		// verifying individuals that are similar to transitively related individuals
		Hashtable similar, result = new Hashtable();
		if (related != null && !related.isEmpty()) {
			for (Enumeration e = related.keys(); e.hasMoreElements(); ) {
				Individual relInd = (Individual) ((RDFNode) e.nextElement()).as(Individual.class);
				similar = fuzOntAccess.getFuzSimilarity(null, relInd, minSim);
				if (similar == null)
					similar = new Hashtable();
				similar.put(relInd, related.get(relInd));
				result.put(relInd, similar);
				
			}
		}
		return result;
		
	}

	
	/* 
	 * returns all individuals which have a transitive relationship with ind
	 * @param ind 			Individual which may be transitive related to other individuals.
	 * @param transProp 	Transitive property.
	 * @param minMembership Mininum membership considering the transitive property.
	 * @return 				Hashtable containing related individuals as keys and their corresponding membership.
	 */
	public Hashtable getTransitivityRelated(Individual ind, OntProperty transProp, float minMembership) {
		
		return fuzOntAccess.getFuzRelDegree(ind, transProp, minMembership);
		
	}
	
	
	/* 
	 * returns all wholes that are close to whole by verifying their parts in common, considering fuzzy membership
	 * @param whole			Individual representing the whole.
	 * @param hasPart		HasPart relation (holonymy) which is going to be analyzed.
	 * @param minCloseness	Minimum closeness required.
	 * @param minSim		The minimum similarity required between parts (getting similar parts).
	 * @param minMemb		The minimum membership required to be considered a part of a whole.
	 * @return 				Hashtable containing all wholes that are close to whole with closeness degree above minCloseness.
	 */
	public Hashtable getCloseWholes(Individual whole, OntProperty hasPart, float minCloseness, float minSim, float minMemb) {
		/*
		 // getting similar wholes by similarTo relation
		  Hashtable result = getSimilarTo(whole, minCloseness);
		  
		  if (result == null)
		  result = new Hashtable();
		  */
		
		Hashtable result = new Hashtable();
		
		// getting all parts of whole, including fuzzy ones
		Hashtable parts = fuzOntAccess.getFuzRelDegree(whole, hasPart, minMemb);
		
		if (parts != null && !parts.isEmpty()) {
			
			float comWhole = 0, comOtherWhole = 0, totalWhole = 0, totalOtherWhole = 0;
			
			// getting total membership degrees of the parts of whole
			for (Enumeration e = parts.keys(); e.hasMoreElements(); ) {			
				totalWhole += ( (Float)parts.get( (Individual)( (RDFNode) e.nextElement() ).as(Individual.class) ) ).floatValue();
			}
			
			// getting all other wholes 
			Vector otherWholes = fuzOntAccess.getWholes();
			/*
			 Hashtable otherWholes = new Hashtable();
			 for (StmtIterator si = ontModel.listStatements((Resource) null, hasPart, (RDFNode) null); si.hasNext(); ) {
			 Statement s = si.nextStatement();
			 if (otherWholes.get(s.getSubject()) == null && !s.getSubject().equals(whole))
			 otherWholes.put(s.getSubject(), s.getSubject().getLocalName());
			 }
			 */
			
			// verifying common parts
			Individual otherWhole, otherWholePart;
			Hashtable otherWholeParts, otherWholeSimParts = new Hashtable(), simParts;
			Float otherWholePartMemb;
			
			// verifying each otherWhole
			for (Enumeration e = otherWholes.elements(); e.hasMoreElements(); ) {
				
				// refreshing counters
				comWhole = 0;
				comOtherWhole = 0;
				totalOtherWhole = 0;
				
				otherWhole = (Individual)((RDFNode)e.nextElement()).as(Individual.class);
				
				if (!whole.equals(otherWhole)) {
					
					// getting all parts of otherWhole
					otherWholeParts = fuzOntAccess.getFuzRelDegree(otherWhole, hasPart, minMemb);
					
					if (otherWholeParts != null || !otherWholeParts.isEmpty()) {
						
						/* for each part of otherWhole:
						 * - consider its membership to sum up totalOtherWhole
						 * - get its similar parts --> simParts
						 * - compare if it is also a part of whole, considering similar parts
						 */			
						for (Enumeration e1 = otherWholeParts.keys(); e1.hasMoreElements();) {
							otherWholePart = (Individual)((RDFNode)e1.nextElement()).as(Individual.class);
							
							// summing up totalOtherWhole
							otherWholePartMemb = (Float) otherWholeParts.get(otherWholePart);
							totalOtherWhole += otherWholePartMemb.floatValue();
							
							// it is a common part
							if ( parts.get(otherWholePart) != null ) {
								comWhole += ((Float)parts.get(otherWholePart)).floatValue();
								comOtherWhole += otherWholePartMemb.floatValue();
								
							}
							// it is not a common part
							else {
								// looking for similar parts
								simParts = (Hashtable) otherWholeSimParts.get(otherWholePart.getLocalName());
								
								// getting similar parts above minSim
								if (simParts == null) 
									simParts = getSimilarTo(otherWholePart, minSim);
								
								if (simParts != null) {
									otherWholeSimParts.put(otherWholePart.getLocalName(), simParts);
									Enumeration e2 = simParts.keys();
									Individual simPart = (Individual) ((RDFNode)e2.nextElement()).as(Individual.class);
									while (e2.hasMoreElements() && parts.get(simPart) == null)
										simPart = (Individual) ((RDFNode)e2.nextElement()).as(Individual.class);
									
									// otherWhole has a similar part in common
									if (parts.get(simPart) != null) {
										comWhole += ((Float)parts.get(simPart)).floatValue();
										comOtherWhole += otherWholePartMemb.floatValue();
									}
								}
							}
						}
						
						// obtaining Closeness degrees
						float closeWhole = comWhole / totalWhole;
						float closeOtherWhole = comOtherWhole / totalOtherWhole;
						
						// calculating closeness by Arithmetic mean
						/*
						float arithMean = (closeWhole + closeOtherWhole)/2;
						if (arithMean >= minCloseness)
							result.put(otherWhole, new Float(arithMean));
						*/
						
						// calculating closeness by Weighted mean (weight = total number of parts)
						
						float weightMean = (comWhole + comOtherWhole) / (totalWhole + totalOtherWhole);
						if (weightMean >= minCloseness)
							result.put(otherWhole, new Float(weightMean));
						
						// consider only closeness degrees above minCloseness
						// calculating closeness by min(v1,v2)
						/*if (closeWhole <= closeOtherWhole && closeWhole >= minCloseness)
							result.put(otherWhole, new Float(closeWhole));
						
						if (closeOtherWhole < closeWhole && closeOtherWhole >= minCloseness)
							result.put(otherWhole, new Float(closeOtherWhole));
						*/
					}
				}
			}
		}
		
		return result;
	}
	
	
	/* 
	 * returns all homonyms from label
	 * @param propValue	Value of property to be verified if there are homonyms.
	 * @param prop		Property that can have homonyms as values.
	 * @return 		Hashtable containing OntResources as keys representing the concepts (classes or individuals)
	 * 				that are homonyms related to propValue and Vectors as paths from the OntResource to superclasses.
	 */
	public Hashtable getHomonyms( String propValue, Property prop ) {
		
		Hashtable result = new Hashtable();
		
		Vector homonyms = fuzOntAccess.getByProp(propValue, prop);
		
		if (!homonyms.isEmpty() && homonyms.size() > 1) {
			
			OntResource ontResource;
			
			for (Iterator i = homonyms.iterator(); i.hasNext(); ) {
				ontResource = (OntResource) ((RDFNode)i.next()).as(OntResource.class);
				
				// getting direct superclasses
				ArrayList dirSuperclasses = new ArrayList();
				
				// ontResource is a class 
				if (ontResource.hasRDFType("http://www.w3.org/2002/07/owl#Class")) {
					OntClass ontClass = (OntClass) ontResource.as(OntClass.class);
					dirSuperclasses = (ArrayList) ((ExtendedIterator) ontClass.listSuperClasses(true)).toList();
				}
				// ontResource is an individual
				else {
					Individual ind = (Individual) ontResource.as(Individual.class);
					dirSuperclasses = (ArrayList) ((ExtendedIterator)ind.listRDFTypes(true)).toList();
				}
		
				Vector superclassPaths = new Vector();
				for (Iterator j = dirSuperclasses.iterator(); j.hasNext(); ) {
					OntClass ontClass = (OntClass) ((RDFNode)j.next()).as(OntClass.class);
					Vector aux = new Vector(), paths = new Vector();
					if (!ontClass.isAnon() && ontClass.getLocalName().compareTo("WholeClass") != 0) {
						identifyPaths(ontClass, aux, paths);
						superclassPaths.addAll(paths);
					}
				}
				
				result.put(ontResource, superclassPaths);
				
			}

		}
	
		
		return result;
		
	}
	
	
	/* 
	 * returns all paths from ontClass to the greatest superclass of the ontology
	 * @param ontClass	Class that is going to be verified to get paths.
	 * @param path		Auxiliary vector to compute paths.
	 * @param result	Vector containing the paths that are represented by vectors of ontClasses.
	 */
	public void identifyPaths( OntClass ontClass, Vector path, Vector result ) {
		
		// direct superclasses 
		ArrayList directSuperclasses = (ArrayList) ( (ExtendedIterator) ontClass.listSuperClasses(true) ).toList();
		path.add(ontClass);
		
		// ontClass has only one direct ancestor 
		if ( directSuperclasses.size() == 1 )  {
			Iterator i = directSuperclasses.iterator();
			OntClass superclass = (OntClass) ((RDFNode) i.next()).as(OntClass.class);
			
			// base case --> direct superclass is owl:Thing
			if ( superclass.getNameSpace().compareTo("http://www.w3.org/2002/07/owl#") == 0 
				 && superclass.getLocalName().compareTo("Thing") == 0  ) {
				
				new SubClassQuicksort().quicksort(path, 0, path.size()-1);
				result.add(path);
				
			}
			
			if ( !superclass.isAnon() && superclass.getNameSpace().compareTo(ontClass.getNameSpace()) == 0
									  && !(superclass.getLocalName().compareTo("FuzzyConcept") == 0)) {
				
				//path.add(superclass);
				identifyPaths(superclass, path, result);
				
			}
		}
		
		// ontClass has more than one direct ancestor --> more than one path
		else {
			//Vector subPaths = new Vector();
			
			for (Iterator i = directSuperclasses.iterator(); i.hasNext(); ) {
				OntClass ontClass1 = (OntClass) ((RDFNode)i.next()).as(OntClass.class);
				
				// base case --> direct superclass is owl:Thing
				if ( ontClass1.getNameSpace().compareTo("http://www.w3.org/2002/07/owl#") == 0 
					 && ontClass1.getLocalName().compareTo("Thing") == 0  ) {
					
					new SubClassQuicksort().quicksort(path, 0, path.size()-1);
					result.add(path);
					
				}
				else if ( !ontClass1.isAnon() && ontClass1.getNameSpace().compareTo(ontClass.getNameSpace()) == 0
						  && !(ontClass1.getLocalName().compareTo("FuzzyConcept") == 0)) {
					
					Vector subPath1 = new Vector();
					subPath1.addAll(path);
					//subPath1.add(ontClass1);
					identifyPaths(ontClass1, subPath1, result);
					//subPaths.add(subPath1);
				}

			}
			
			//paths.clear();
			//paths.addAll(subPaths);

		}
	}
	
	
	public int getTransitiveDeepness( Individual ind, OntProperty prop ) {
		return fuzOntAccess.getTransitiveDeepness(ind, null, prop, 0);
	}
	
	public int getTransitiveDeepness( Individual ind1, Individual ind2, OntProperty prop ) {
		return fuzOntAccess.getTransitiveDeepness(ind1, ind2, prop, 0);
	}
	
	public Hashtable getDirectlyRelated( Individual ind, OntProperty prop, float minMembership ) {
		
		Hashtable result = new Hashtable();
		
		Hashtable directRelated = fuzOntAccess.getDirRelated(ind, prop);
		Hashtable directRelatedDegree = fuzOntAccess.getDirFuzRelDegree(ind, prop);
		
		for (Enumeration e = directRelated.keys(); e.hasMoreElements();) {
			String name = (String) e.nextElement();
			Individual related = (Individual) directRelated.get(name);
			Float memb = (Float) directRelatedDegree.get(related.getLocalName());
			if (memb == null)
				result.put(related, new Float(1));
			else {
				if( memb.floatValue() >= minMembership )						
					result.put(related, memb);
			}
				
		}
		
		
		return result;
	}
	
	
}
